﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Text.RegularExpressions;
using System.Web.Script.Serialization;

namespace Logica101.Controllers
{
    public class InicioController : Controller
    {

        public static string clCaracteresValidos = @"[^A-Za-z0-9.+~\^()\s]+";
        public static string clCompuertas = @"(\.|and|\+|or|xor|nand)";
        public static string clVariable = @"([A-z][0-9]*)";
        public static string clLetra = @"^([A-z])$";
        public static string clNVariable = @"(~|not)" + clVariable;
        public static string clDupla = clVariable + clCompuertas + clVariable;
        public static string clDuplaA = clVariable + @"(and|\.)" + clVariable;
        public static string clDuplaO = clVariable + @"(or|\+)" + clVariable;
        public static string clDuplaX = clVariable + @"(xor|\^)" + clVariable;
        public static string clDuplaN = clVariable + @"(nand)" + clVariable;
        public static string clParenOpn = @"\(";
        public static string clParenClo = @"\)";
        public static string clParenDupla = clParenOpn + clDupla + clParenClo;

        public const int OP_AND = 1;
        public const int OP_OR = 2;
        public const int OP_NOT = 3;
        public const int OP_XOR = 4;
        public const int OP_NAND = 5;

        public List<Nodo> nodos;
        public List<String> tabla;
        public List<String> tabla_html;
        public List<String> letras;
        public List<Boolean> val;
        public List<List<Boolean>> l_val;
        public Nodo tope;

        /*
         * Método inicial de la aplicacion y el controlador Inicio
         */
        public ActionResult Index()
        {
            return View();
        }

        /*
         * Método invocado para realizar el procesamiento principal
         */
        [HttpPost]
        public ActionResult Validar(string expresion)
        {

            RespuestaValidacion ret = new RespuestaValidacion();

            nodos = new List<Nodo>();
            tabla = new List<String>();
            letras = new List<String>();
            val = new List<Boolean>();
            tabla_html = new List<string>();
            l_val = new List<List<Boolean>>();
            string test_ex = "";

            ViewData["error"] = "";

            Regex clValido = new Regex(clCaracteresValidos);

            if (clValido.Replace(expresion, "") != expresion)
                ViewData["error"] += "Se encontraron caracteres inválidos en la expresión: '" + clValido.Replace(expresion, "") + "'";
            else
            {
                test_ex = _procesarExpresion(expresion);

                ret.valida = 0;
                ViewData["expresion"] = "";
                if (Regex.IsMatch(test_ex, @"^" + clVariable + @"$"))
                {
                    ret.valida = 1;
                    tope = nodos.Find(element => element._id == test_ex);
                    _tabla(tope);

                    letras.Sort();

                    foreach (String th in letras)
                    {
                        tabla_html.Add(th);
                    }
                    foreach (String th in tabla)
                    {
                        if (!letras.Exists(letra => letra == th))
                            tabla_html.Add(th);
                    }

                    if (tope != null)
                    {

                        ViewData["expresion"] = tope.expresion();
                        int paso = 0;
                        iterarValores(0, ref paso);
                    }
                    test_ex += "\n" + imprimir_nodos(tope);

                }
                else
                {
                    foreach (Nodo nod in nodos)
                        test_ex += "\n" + imprimir_nodos(nod);
                    ViewData["error"] = "Expresión con errores de Sintaxis";
                }

                foreach (string letra in letras)
                {
                    test_ex += "LETRA: " + letra + "<br>";
                }
            }

            ViewData["tabla"] = tabla_html;
            ViewData["evaluacion"] = l_val;

            ret.debug = test_ex;
            ret.expresiones = tabla;

            JavaScriptSerializer serializador = new JavaScriptSerializer();

            //return serializador.Serialize(ret);
            ViewData["json"] = "var datos=" + serializador.Serialize(ret) + ";";
            ViewData["test"] = "var test=" + serializador.Serialize(l_val) + ";";

            return PartialView("_ajax_validar");

        }

        private void iterarValores(int ini,ref int paso)
        {

            Boolean nval = false;
            Boolean rval = false;

            if (ini > letras.Count - 1)
                return;

            for (int x = 1; x >= 0; x--)
            {
                nval = (x == 0 ? false : true);

                if (ini > val.Count - 1)
                {
                    val.Insert(ini, nval);
                }
                else
                {
                    val[ini] = nval;
                }

                if (ini == letras.Count - 1)
                {

                    if (l_val.Count - 1 < paso)
                        l_val.Insert(paso, new List<Boolean>());


                    for (int y = 0; y < tabla_html.Count; y++)
                    {

                        rval = nodos.Find(nodo => nodo.expresion() == tabla_html[y]).evaluar(this);

                        if (l_val[paso].Count == y)
                            l_val[paso].Insert(y, rval);
                        else 
                            l_val[paso][y] = rval;
                    }

                    paso++;

                }
                else
                {
                    iterarValores(ini + 1, ref paso);
                }

            }
        }

        /*
         * Función para permitir la depuración e impresión de los tokens
         */

        private string imprimir_nodos(Nodo nod, int niv = 0)
        {
            string ret = "";

            if (nod == null)
                return "";

            ret = new String('\t', niv) + "Token: " + nod._id;

            if (nod._op != 0)
            {
                ret += "\n" + new String('\t', niv);
                ret += "Expresion: " + nod.expresion();
                ret += "\n" + new String('\t', niv);
                ret += "Componentes: \n";
                ret += imprimir_nodos(nod._izq, niv + 1) + "<br>";

                ret += new String('\t', niv + 1) + "Operación: ";

                switch (nod._op)
                {
                    case OP_AND: ret += "AND";
                        break;
                    case OP_OR: ret += "OR";
                        break;
                    case OP_NOT: ret += "NOT";
                        break;
                    case OP_XOR: ret += "XOR";
                        break;
                }

                ret += "<br>";

                ret += imprimir_nodos(nod._der, niv + 1) + "<br>";

            }

            return ret;
        }

        /*
         * Esta función se encarga de analizar la expresión y crear los nodos con las 
         * operaciones que se deben realizar, es el interprete inicial
         */

        private string _procesarExpresion(string expresion)
        {

            int rgidx = 1;
            int pl;
            Regex mireg;

            string debug = " dbg: ";

            do
            {

                pl = 0;

                // Eliminar Espacios


                // Eliminar negaciones consecutivas
                while (Regex.Replace(expresion, @"(~|not){2}", "").Length < expresion.Length)
                    expresion = Regex.Replace(expresion, @"(~|not){2}", "");

                // Probar negaciones
                MatchCollection mneg = Regex.Matches(expresion, clNVariable, RegexOptions.IgnoreCase);

                foreach (Match mn in mneg)
                {
                    Nodo tmpnodo = _obtenerNodo(mn.Groups[2].Value);
                    Nodo tmpnodo_2 = _obtenerNodo("n" + rgidx);
                    tmpnodo_2._der = tmpnodo;
                    tmpnodo_2._op = OP_NOT;

                    expresion = Regex.Replace(expresion, mn.Groups[0].Value, "n" + rgidx);
                    rgidx++;
                }

                if (mneg.Count > 0)
                {
                    pl++;
                    continue;
                }


                // Probar Paréntesis
                MatchCollection mpar = Regex.Matches(expresion, clParenDupla, RegexOptions.IgnoreCase);

                foreach (Match mp in mpar)
                {
                    // Item izquierdo
                    Nodo izq = nodos.Find(elemento => elemento._id == mp.Groups[1].Value);
                    if (izq == null)
                    {
                        izq = _obtenerNodo(mp.Groups[1].Value);
                    }

                    // Operador
                    int op = 0;
                    string op_rep = "";
                    if (Regex.IsMatch(mp.Groups[2].Value, @"and|\.", RegexOptions.IgnoreCase))
                    {
                        op = OP_AND;
                        op_rep = @"(and|\.)";
                    }
                    else if (Regex.IsMatch(mp.Groups[2].Value, @"or|\+", RegexOptions.IgnoreCase))
                    {
                        op = OP_OR;
                        op_rep = @"(or|\+)";
                    }
                    else if (Regex.IsMatch(mp.Groups[2].Value, @"xor", RegexOptions.IgnoreCase))
                    {
                        op = OP_XOR;
                        op_rep = "(xor)";
                    }

                    // Item derecho
                    Nodo der = nodos.Find(elemento => elemento._id == mp.Groups[3].Value);
                    if (der == null)
                    {
                        der = _obtenerNodo(mp.Groups[3].Value);
                    }

                    Nodo tmpnodo = new Nodo("n" + rgidx, izq, der, op);
                    tmpnodo._par = true;
                    nodos.Add(tmpnodo);

                    mireg = new Regex(@"\(" + mp.Groups[1].Value + op_rep + mp.Groups[3].Value + @"\)");
                    expresion = Regex.Replace(expresion, @"\(" + mp.Groups[1].Value + op_rep + mp.Groups[3].Value + @"\)", "n" + rgidx);
                    rgidx++;


                }

                if (mpar.Count > 0)
                {
                    pl++;
                    continue;
                }

                // Procesar Dupla AND
                MatchCollection mdupa = Regex.Matches(expresion, clDuplaA, RegexOptions.IgnoreCase);

                foreach (Match mp in mdupa)
                {
                    // Item izquierdo
                    Nodo izq = nodos.Find(elemento => elemento._id == mp.Groups[1].Value);
                    if (izq == null)
                    {
                        izq = _obtenerNodo(mp.Groups[1].Value);
                    }

                    // Operador
                    int op = 0;
                    string op_rep = "";
                    if (Regex.IsMatch(mp.Groups[2].Value, @"and|\.", RegexOptions.IgnoreCase))
                    {
                        op = OP_AND;
                        op_rep = @"(and|\.)";
                    }
                    else if (Regex.IsMatch(mp.Groups[2].Value, @"or|\+", RegexOptions.IgnoreCase))
                    {
                        op = OP_OR;
                        op_rep = @"(or|\+)";
                    }
                    else if (Regex.IsMatch(mp.Groups[2].Value, @"xor", RegexOptions.IgnoreCase))
                    {
                        op = OP_XOR;
                        op_rep = @"(xor)";
                    }

                    // Item derecho
                    Nodo der = nodos.Find(elemento => elemento._id == mp.Groups[3].Value);
                    if (der == null)
                    {
                        der = _obtenerNodo(mp.Groups[3].Value);
                    }

                    Nodo tmpnodo = new Nodo("n" + rgidx, izq, der, op);
                    nodos.Add(tmpnodo);

                    debug += " " + mp.Groups[1].Value + op_rep + mp.Groups[3].Value;

                    expresion = Regex.Replace(expresion, mp.Groups[1].Value + op_rep + mp.Groups[3].Value, "n" + rgidx);
                    rgidx++;

                }

                if (mdupa.Count > 0)
                {
                    pl++;
                    continue;
                }

                // Procesar Dupla OR
                MatchCollection mdupo = Regex.Matches(expresion, clDuplaO, RegexOptions.IgnoreCase);

                debug += clDupla + " => " + expresion;

                foreach (Match mp in mdupo)
                {
                    // Item izquierdo
                    Nodo izq = nodos.Find(elemento => elemento._id == mp.Groups[1].Value);
                    if (izq == null)
                    {
                        izq = _obtenerNodo(mp.Groups[1].Value);
                    }

                    // Operador
                    int op = 0;
                    string op_rep = "";
                    if (Regex.IsMatch(mp.Groups[2].Value, @"and|\.", RegexOptions.IgnoreCase))
                    {
                        op = OP_AND;
                        op_rep = @"(and|\.)";
                    }
                    else if (Regex.IsMatch(mp.Groups[2].Value, @"or|\+", RegexOptions.IgnoreCase))
                    {
                        op = OP_OR;
                        op_rep = @"(or|\+)";
                    }
                    else if (Regex.IsMatch(mp.Groups[2].Value, @"xor", RegexOptions.IgnoreCase))
                    {
                        op = OP_XOR;
                        op_rep = @"(xor)";
                    }

                    // Item derecho
                    Nodo der = nodos.Find(elemento => elemento._id == mp.Groups[3].Value);
                    if (der == null)
                    {
                        der = _obtenerNodo(mp.Groups[3].Value);
                    }

                    Nodo tmpnodo = new Nodo("n" + rgidx, izq, der, op);
                    nodos.Insert(nodos.Count, tmpnodo);

                    expresion = Regex.Replace(expresion, mp.Groups[1].Value + op_rep + mp.Groups[3].Value, "n" + rgidx);
                    rgidx++;

                }

                if (mdupo.Count > 0)
                {
                    pl++;
                    continue;
                }

            } while (Regex.Match(expresion, @"^" + clVariable + "$", RegexOptions.IgnoreCase).Groups.Count == 1 && pl > 0);

            return expresion;

        }

        /*
         * Recorre los nodos en Post-Orden, para realizar la respectiva lista de encabezados de la tabla.
         */
        private void _tabla(Nodo nod)
        {
            if (nod == null)
                return;
            if (nod._izq != null)
                _tabla(nod._izq);

            if (nod._der != null)
                _tabla(nod._der);

            tabla.Add(nod.expresion());


        }

        /*
         * Crea un nodo y a su vez registra la posible variable unica (LETRA)
         */

        private Nodo _obtenerNodo(string id)
        {
            Nodo ret = nodos.Find(element => element._id == id);

            if (ret == null)
            {
                ret = new Nodo(id);

                nodos.Add(ret);

                if (Regex.IsMatch(id, clLetra) && !letras.Exists(letra => letra == id))
                    letras.Add(id);
            }

            return ret;

        }

    }

    /*
     * Nodos del Arbol 
     */

    public class Nodo
    {

        public string _id = "";
        public bool _par = false;
        public Nodo _izq = null;
        public Nodo _der = null;
        public Nodo _sup = null;

        public int _op = 0;

        public Nodo(String id, Nodo izq = null, Nodo der = null, int op = 0)
        {
            _id = id;

            if (izq != null && der != null && op != 0)
            {
                _izq = izq;
                _der = der;
                _op = op;
            }


        }

        public string expresion()
        {
            string ret = "";

            switch (_op)
            {
                case 0:
                    ret = this._id;
                    break;
                case InicioController.OP_AND:
                    ret = _izq.expresion() + " . " + _der.expresion();
                    break;
                case InicioController.OP_OR:
                    ret = _izq.expresion() + " + " + _der.expresion();
                    break;
                case InicioController.OP_NOT:
                    ret = "~" + _der.expresion();
                    break;
                case InicioController.OP_XOR:
                    ret = _izq.expresion() + " XOR " + _der.expresion();
                    break;
                case InicioController.OP_NAND:
                    ret = _izq.expresion() + " NAND " + _der.expresion();
                    break;
            }

            if (_par)
                ret = "(" + ret + ")";

            return ret;

        }

        public Boolean evaluar(InicioController inicio)
        {
            Boolean ret = false;
            int cidx = -1;

            switch (_op)
            {
                case 0:
                    cidx = inicio.letras.IndexOf(_id);
                    ret = inicio.val[cidx];
                    break;
                case InicioController.OP_NOT:
                    ret = !_der.evaluar(inicio);
                    break;
                case InicioController.OP_AND:
                    ret = _izq.evaluar(inicio) & _der.evaluar(inicio);
                    break;
                case InicioController.OP_OR:
                    ret = _izq.evaluar(inicio) | _der.evaluar(inicio);
                    break;
                case InicioController.OP_XOR:
                    ret = _izq.evaluar(inicio) ^ _der.evaluar(inicio);
                    break;
                case InicioController.OP_NAND:
                    ret = !(_izq.evaluar(inicio) & _der.evaluar(inicio));
                    break;
            }

            /*eidx = inicio.tabla_html.IndexOf(expresion());

            if (inicio.l_val.Count == paso)
                inicio.l_val.Insert(paso, new List<Boolean>());
            if (inicio.l_val[paso].Count == eidx)
                inicio.l_val[paso].Insert(eidx, ret);*/

            return ret;
        }

    }


    public struct RespuestaValidacion
    {
        public int valida { get; set; }
        public List<string> expresiones { get; set; }
        public string debug { get; set; }
    }
}
